﻿using System;
using System.Globalization;
using System.IO.Ports;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Threading;
using Microsoft.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore;
using MilkTeaMachine.OfflineClient.EntityFrameworkCore.Models;
using MilkTeaMachine.OfflineClient.Models;
using MilkTeaMachine.OfflineClient.Utils;
using NModbus;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;

namespace MilkTeaMachine.OfflineClient.ViewModels;

public class IngredientCalibrationViewModel : ViewModelBase, IRoutableViewModel
{
    private IngredientCalibrationViewModel(IScreen hostScreen)
    {
        _db = Locator.Current.GetService<ApplicationDbContext>()!;
        HostScreen = hostScreen;
        SaveAndQuitCmd = ReactiveCommand.CreateFromTask(SaveAndQuitAsync);
        GoBackCmd = ReactiveCommand.CreateFromObservable(() =>
            HostScreen.Router.NavigateBack.Execute());
        GoWeightingCalibrationCmd = ReactiveCommand.CreateFromObservable(() =>
            HostScreen.Router.Navigate.Execute(new WeightingCalibrationViewModel(HostScreen, 0x01)));
        CalibrationCmd =
            ReactiveCommand.CreateFromTask(async (string rotation) => await CalibrationAsync(int.Parse(rotation)));
    }

    public IngredientCalibrationViewModel() : this(default!)
    {
    }

    private async Task CalibrationAsync(int rotation)
    {
        try
        {
#if DEBUG
#else
        var parser = new IniFileParser.IniFileParser();
        var data = parser.ReadFile("Configuration/config.ini");
        var speed = Convert.ToUInt16(_ingredient.OutSpeed);
        var acceleration = Convert.ToByte(data["Machine"]["acceleration"]);
        var direction = Convert.ToByte(data["Machine"]["direction"]);
        var directionAndAcceleration = App.GetComposedUshort(direction, acceleration);
        var needPulse = Convert.ToUInt32(rotation * 3200);
        var pulseHigh = (ushort)((needPulse >> 16) & 0xFFFF); // 获取高16位
        var pulseLow = (ushort)(needPulse & 0xFFFF); // 获取低16位

        var realAddress = data["Address"][$"Address{_ingredient.DeviceAddress}"];
        using (var master = IModbusSerialFactory.Create())
        {
            await master.WriteMultipleRegistersAsync(
                byte.Parse(realAddress),
                0x00FD,
                [directionAndAcceleration, speed, pulseHigh, pulseLow, 0]);
        }
#endif
            switch (rotation)
            {
                case 1:
                    Weight1 = await GetWeightAsync(1);
                    break;
                case 3:
                    Weight2 = await GetWeightAsync(3);
                    break;
                case 5:
                    Weight3 = await GetWeightAsync(4);
                    break;
                case 8:
                    Weight4 = await GetWeightAsync(8);
                    break;
                case 10:
                    Weight5 = await GetWeightAsync(10);
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(rotation));
            }
        }
        catch (Exception e)
        {
            Dispatcher.UIThread.InvokeAsync(async () =>
            {
                await MessageBoxUtil.GetBox(e.StackTrace).ShowWindowAsync().ConfigureAwait(true);
            });
        }
    }

    private async Task<string> GetWeightAsync(int rotation)
    {
// #if DEBUG
//         return (new Random().NextDouble() * 20).ToString(CultureInfo.InvariantCulture);
// #endif
        var parser = new IniFileParser.IniFileParser();
        var data = parser.ReadFile("Configuration/config.ini");
        ushort[]? weight;
        byte[] highBits;
        byte[] lowBits;
        int weightValue;
        //常规称重
        if (_weightType != WeightTypeEnum.冰水 && _weightType != WeightTypeEnum.热水 && _weightType != WeightTypeEnum.冰块)
        {
            //获取转多少圈
            var needPulse = Convert.ToUInt32(rotation * 3200);
            var pulseHigh = (ushort)((needPulse >> 16) & 0xFFFF); // 获取高16位
            var pulseLow = (ushort)(needPulse & 0xFFFF); // 获取低16位

            //获取地址对应的地址
            var realAddress = byte.Parse(data["Address"][$"Address{_ingredient.DeviceAddress}"]);
            var acceleration = Convert.ToByte(data["Machine"]["acceleration"]);
            var direction = Convert.ToByte(data["Machine"]["direction"]);
            var directionAndAcceleration = App.GetComposedUshort(direction, acceleration);
            var speed = Convert.ToUInt16(_ingredient.OutSpeed);
            using (var master = IModbusSerialFactory.Create())
            {
                await master.WriteMultipleRegistersAsync(
                    realAddress,
                    0x00FD,
                    [directionAndAcceleration, speed, pulseHigh, pulseLow, 0]);
                await AutoRunningScanOrderViewModel.WaitAllFinishedAsync([realAddress], master).ConfigureAwait(true);
            }

            var weightAddress = Convert.ToByte(_weightType == WeightTypeEnum.冰块 ? 0x02 : 0x01);
            using (var weightMaster = IModbusSerialFactory.Create(9600))
            {
                weight = await weightMaster.ReadHoldingRegistersAsync(weightAddress, 0x01, 2);
            }

            if (weight is null)
            {
                _ = Dispatcher.UIThread.InvokeAsync(async () =>
                {
                    await MessageBoxUtil.GetBox("读取称重失败").ShowWindowAsync().ConfigureAwait(true);
                });
                return 0.00.ToString(CultureInfo.InvariantCulture);
            }

            highBits = BitConverter.GetBytes(weight[0]);
            lowBits = BitConverter.GetBytes(weight[1]);
            weightValue = BitConverter.ToInt32([.. lowBits, .. highBits]);
            weightValue = Convert.ToInt32(weightValue * Convert.ToDouble(data["Weight"]["Multiple"]));
            return Math.Round(weightValue / 10.0, 1).ToString(CultureInfo.InvariantCulture);
        }

        //冰水、热水、冰块称重
        var modbusAddress = Convert.ToByte(data["BoolInternalAddress"]["ModbusAddress"]);
        var coldWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["coldWater"]);
        var hotWaterAddress = Convert.ToByte(data["BoolInternalAddress"]["hotWater"]);
        var iceAddress = Convert.ToByte(data["BoolInternalAddress"]["ice"]);
        var address = _weightType switch

        {
            WeightTypeEnum.冰块 => iceAddress,
            WeightTypeEnum.冰水 => coldWaterAddress,
            WeightTypeEnum.热水 => hotWaterAddress,
            _ => throw new ArgumentOutOfRangeException()
        };
        //打开
        using (var digitalControlMaster = IModbusSerialFactory.Create(9600, Parity.Even))
        {
            await digitalControlMaster.WriteSingleCoilAsync(modbusAddress, address, true).ConfigureAwait(false);
            await Task.Delay(TimeSpan.FromSeconds(rotation * 1));
            //关闭
            await digitalControlMaster.WriteSingleCoilAsync(modbusAddress, address, false).ConfigureAwait(false);
        }

        using (var weightMaster = IModbusSerialFactory.Create(9600))
        {
            weight = await weightMaster.ReadHoldingRegistersAsync(0x01, 0x01, 2);
        }

        if (weight is null)
        {
            _ = Dispatcher.UIThread.InvokeAsync(async () =>
            {
                await MessageBoxUtil.GetBox("读取称重失败").ShowWindowAsync().ConfigureAwait(true);
            });
            return 0.00.ToString(CultureInfo.InvariantCulture);
        }

        highBits = BitConverter.GetBytes(weight[0]);
        lowBits = BitConverter.GetBytes(weight[1]);
        weightValue = BitConverter.ToInt32([.. lowBits, .. highBits]);
        weightValue = Convert.ToInt32(weightValue * Convert.ToDouble(data["Weight"]["Multiple"]));
        return Math.Round(weightValue / 10.0, 1).ToString(CultureInfo.InvariantCulture);
    }

    private async Task SaveAndQuitAsync()
    {
        var db = Locator.Current.GetService<ApplicationDbContext>()!;
        db.Attach(_ingredient);
        _ingredient.TurnsPerGram =
            Convert.ToInt32(Convert.ToDouble(Weight1) + Convert.ToDouble(Weight2) + Convert.ToDouble(Weight3) +
                            Convert.ToDouble(Weight4) + Convert.ToDouble(Weight5)) / (1 + 3 + 5 + 8 + 10);
        db.Update(_ingredient);
        await db.SaveChangesAsync();
        HostScreen.Router.NavigateAndReset.Execute(new MainPageViewModel(HostScreen));
    }

    public IngredientCalibrationViewModel(IScreen hostScreen, Ingredient vo) : this(hostScreen)
    {
        _ingredient = vo;
        Address = vo.DeviceAddress switch
        {
            > 10 and < 16 => $"B12{5 + (vo.DeviceAddress - 11)}",
            > 15 and < 17 => "B130",
            > 20 and < 25 => $"B10{vo.DeviceAddress - 20}",
            > 30 and < 35 => $"B10{vo.DeviceAddress - 26}",
            > 40 and < 42 => $"B10{vo.DeviceAddress - 32}",
            > 41 and < 45 => $"B1{vo.DeviceAddress - 32}",
            > 50 and < 55 => $"B1{vo.DeviceAddress - 38}",
            _ => throw new ArgumentOutOfRangeException()
        };
        IngredientName = vo.IngredientName;
    }

    public IngredientCalibrationViewModel(IScreen hostScreen, WeightTypeEnum weightType) : this(hostScreen)
    {
        var parser = new IniFileParser.IniFileParser();
        var data = parser.ReadFile("Configuration/config.ini");
        Address = weightType switch
        {
            WeightTypeEnum.冰块 => data["AddressDisplay"]["ice"],
            WeightTypeEnum.冰水 => data["AddressDisplay"]["coldWater"],
            WeightTypeEnum.热水 => data["AddressDisplay"]["hotWater"],
            _ => throw new ArgumentOutOfRangeException(nameof(weightType), weightType, null)
        };
        _weightType = weightType;
        IngredientName = weightType.ToString();
        var db = Locator.Current.GetService<ApplicationDbContext>()!;
        _ingredient = db.Ingredients.First(i => i.IngredientName == IngredientName);
    }

    private readonly WeightTypeEnum _weightType;
    private readonly ApplicationDbContext _db;
    private readonly Ingredient _ingredient;
    public string? UrlPathSegment { get; } = nameof(IngredientCalibrationViewModel);
    public IScreen HostScreen { get; }
    [Reactive] public string Weight1 { get; set; } = "0.1";
    [Reactive] public string Weight2 { get; set; } = "0.2";
    [Reactive] public string Weight3 { get; set; } = "0.3";
    [Reactive] public string Weight4 { get; set; } = "0.4";
    [Reactive] public string Weight5 { get; set; } = "0.5";
    public ReactiveCommand<Unit, Unit> SaveAndQuitCmd { get; }
    public ReactiveCommand<Unit, IRoutableViewModel> GoWeightingCalibrationCmd { get; }
    public ReactiveCommand<Unit, IRoutableViewModel> GoBackCmd { get; }
    [Reactive] public string Address { get; set; }
    [Reactive] public string IngredientName { get; set; }
    public ReactiveCommand<string, Unit> CalibrationCmd { get; }
}